package org.zhaya.sdk.processors;

import cn.hutool.core.util.StrUtil;
import com.google.common.base.Enums;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.logz.sawmill.*;
import io.logz.sawmill.annotations.ProcessorProvider;
import io.logz.sawmill.exceptions.ProcessorConfigurationException;
import io.logz.sawmill.parser.ConditionDefinition;
import io.logz.sawmill.parser.ConditionDefinitionParser;
import io.logz.sawmill.parser.ConditionParser;
import io.logz.sawmill.utilities.JsonUtils;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.zhaya.sdk.DodpProcessor;
import org.zhaya.sdk.constant.OperatorEnum;
import org.zhaya.sdk.processors.condition.ProcessorEnum;
import org.zhaya.sdk.swamill.FactoriesRegister;

import javax.inject.Inject;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

@ProcessorProvider(type = "compareAndSet", factory = CompareAndSetProcessor.Factory.class)
public class CompareAndSetProcessor implements Processor {

    private final List<Map<String, Object>> conditionDefinitionMapList;
    private final ConditionParser parser;

    public CompareAndSetProcessor(List<Map<String, Object>> conditionDefinitionMapList) {
        this.conditionDefinitionMapList = conditionDefinitionMapList;
        this.parser = new ConditionParser(FactoriesRegister.conditionFactoryRegistry);
    }

    @Override
    public ProcessResult process(Doc doc) {
        for (Map<String, Object> map : conditionDefinitionMapList) {
            boolean evaluate;
            Map<String, Object> conditionDefinition = (Map<String, Object>) map.get(ProcessorEnum.map.name());
            if (Objects.nonNull(conditionDefinition)) {
                io.logz.sawmill.Condition.Factory factory = FactoriesRegister.conditionFactoryRegistry.get((String) conditionDefinition.get(ProcessorEnum.type.name()));
                io.logz.sawmill.Condition condition = factory.create(conditionDefinition, parser);
                evaluate = condition.evaluate(doc);
            } else {
                evaluate = true;
            }

            if (evaluate) {
                Template targetField = (Template) map.get(ProcessorEnum.targetField.name());
                Function<Doc, Object> targetValue = (Function<Doc, Object>) map.get(ProcessorEnum.targetValue.name());
                if (Objects.isNull(targetField) || StringUtils.isBlank(targetField.render(doc))) {
                    return ProcessResult.success();
                }
                doc.addField(targetField.render(doc), targetValue.apply(doc));
                return ProcessResult.success();
            }
        }
        return ProcessResult.success();
    }

    public static class Factory implements DodpProcessor.Factory {
        private final TemplateService templateService;

        @Inject
        public Factory(TemplateService templateService) {
            this.templateService = templateService;
        }

        @Override
        public Processor create(Map<String, Object> config) {
            List<Map<String, Object>> criterias = (List) config.get(ProcessorEnum.criteria.name());

            List<Map<String, Object>> conditionMapList = Lists.newArrayList();
            for (Map<String, Object> criteria : criterias) {
                String logic = criteria.get(ProcessorEnum.logic.name()).toString();
                Map<String, Object> then = (Map<String, Object>) criteria.get(ProcessorEnum.then.name());
                String targetField = then.get(ProcessorEnum.targetField.name()).toString();
                Object targetValue = then.get(ProcessorEnum.targetValue.name());

                List<Map<String, Object>> conditions = (List<Map<String, Object>>) criteria.get(ProcessorEnum.condition.name());
                if (CollectionUtils.isEmpty(conditions) && ProcessorEnum.and.name().equals(logic)) {
                    if (StringUtils.isBlank(targetField)) {
                        throw new ProcessorConfigurationException("failed to parse compareAndSet config, cannot be processed without targetFields");
                    }
                    Map<String, Object> map = Maps.newHashMap();
                    map.put(ProcessorEnum.map.name(), null);
                    map.put(ProcessorEnum.targetField.name(), templateService.createTemplate(targetField));
                    map.put(ProcessorEnum.targetValue.name(), createGetValueFunction(targetValue));
                    conditionMapList.add(map);
                } else {
                    Map<String, Object> conditionDefinition = Maps.newHashMap();
                    List<Map<String, Object>> compares = Lists.newArrayList();

                    for (Map<String, Object> condition : conditions) {
                        String operator = condition.get(ProcessorEnum.operator.name()).toString();
                        String field = condition.get(ProcessorEnum.field.name()).toString();
                        Object value = condition.get(ProcessorEnum.value.name());
                        Map<String, Object> transfer = Enums.getIfPresent(OperatorEnum.class, operator)
                                .toJavaUtil().orElseThrow(() -> new ProcessorConfigurationException("failed to covert condition enum, please check the operator: {" + operator + StrUtil.DELIM_END))
                                .transfer(field, value);
                        compares.add(transfer);
                    }
                    Map<String, Object> conditionMap = Maps.newHashMap();
                    conditionMap.put(logic, compares);
                    List<Map<String, Object>> configList = JsonUtils.getList(conditionMap, logic, true);
                    List<ConditionDefinition> conditionDefinitionList = configList.stream().map(ConditionDefinitionParser::parse).collect(Collectors.toList());
                    conditionDefinition.put(ProcessorEnum.type.name(), logic);
                    conditionDefinition.put(ProcessorEnum.conditions.name(), conditionDefinitionList);

                    Map<String, Object> map = Maps.newHashMap();
                    map.put(ProcessorEnum.map.name(), conditionDefinition);
                    map.put(ProcessorEnum.targetField.name(), templateService.createTemplate(targetField));
                    map.put(ProcessorEnum.targetValue.name(), createGetValueFunction(targetValue));
                    conditionMapList.add(map);
                }
            }

            return new CompareAndSetProcessor(conditionMapList);
        }

        private Function<Doc, Object> createGetValueFunction(Object value) {
            if (value instanceof String) {
                Template valueTemplate = templateService.createTemplate((String) value);
                return valueTemplate::render;
            } else {
                return (ignoredDoc) -> value;
            }
        }
    }

    @Data
    @Builder
    public static class Configuration implements Processor.Configuration {
        private final List<Criteria> criteria;
    }

    @Data
    @Builder
    public static class Criteria {
        private final String logic;
        private final List<Condition> condition;
        private final Then then;
    }

    @Data
    @Builder
    public static class Condition {
        private final String operator;
        private final String field;
        private final Object value;
    }

    @Data
    @Builder
    public static class Then {
        private final String targetField;
        private final Object targetValue;
    }
}